/*
 * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.sun.tools.javac.util;

import javax.tools.JavaFileObject;

import com.sun.tools.javac.file.BaseFileObject;
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticSource;
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticType;

/**
 * A formatter for diagnostic messages. The formatter will format a diagnostic
 * according to one of two format strings, depending on whether or not the
 * source name and position are set. The format is a printf-like string, with
 * the following special characters:
 * <ul>
 * <li>%b: the base of the source name, or "-" if not set
 * <li>%f: the source name, or "-" if not set
 * <li>%l: the line number of the diagnostic, derived from the character offset
 * if set, or "-" otherwise
 * <li>%c: the column number of the diagnostic, derived from the character
 * offset if set, or "-" otherwise
 * <li>%o: the character offset of the diagnostic if set, or "-" otherwise
 * <li>%p: the prefix for the diagnostic, derived from the diagnostic type
 * <li>%t: the prefix as it normally appears in standard diagnostics. In this
 * case, no prefix is shown if the type is ERROR and if a source name is set
 * <li>%m: the text or the diagnostic, including any appropriate arguments
 * </ul>
 */
public class DiagnosticFormatter {
	/**
	 * A format string to be used for diagnostics with a given position.
	 */
	protected String posFormat;

	/**
	 * A format string to be used for diagnostics regarding classfiles
	 */
	protected String classFormat = DEFAULT_CLASS_FORMAT;

	/**
	 * A format string to be used for diagnostics without a given position.
	 */
	protected String noPosFormat;

	/**
	 * A value to indicate whether to output the i18n key and args, instead of
	 * the derived l10n message.
	 */
	protected boolean raw;

	/** The context key for the formatter. */
	protected static final Context.Key<DiagnosticFormatter> formatterKey = new Context.Key<DiagnosticFormatter>();

	/** Get the DiagnosticFormatter instance for this context. */
	public static DiagnosticFormatter instance(Context context) {
		DiagnosticFormatter instance = context.get(formatterKey);
		if (instance == null)
			instance = new DiagnosticFormatter(context);
		return instance;
	}

	/**
	 * Create a formatter based on the supplied options.
	 */
	protected DiagnosticFormatter(Context context) {
		Options options = Options.instance(context);
		raw = options.get("rawDiagnostics") != null;
		String fmt = options.get("diags");
		if (fmt != null) {
			int sep = fmt.indexOf('|');
			if (sep == -1)
				posFormat = noPosFormat = fmt;
			else {
				posFormat = fmt.substring(0, sep);
				noPosFormat = fmt.substring(sep + 1);
			}
		} else {
			posFormat = DEFAULT_POS_FORMAT;
			noPosFormat = DEFAULT_NO_POS_FORMAT;
		}
	}

	public static final String DEFAULT_POS_FORMAT = "%f:%l: %t%m";
	public static final String DEFAULT_CLASS_FORMAT = "%f: %t%m";
	public static final String DEFAULT_NO_POS_FORMAT = "%p%m";

	public DiagnosticFormatter() {
		posFormat = DEFAULT_POS_FORMAT;
		noPosFormat = DEFAULT_NO_POS_FORMAT;
		raw = false;
	}

	public DiagnosticFormatter(String pos, String noPos) {
		posFormat = pos;
		noPosFormat = noPos;
		raw = false;
	}

	String format(JCDiagnostic d) {
		return (raw ? format_raw(d) : format_std(d));
	}

	private String format_raw(JCDiagnostic d) {
		DiagnosticSource source = d.getDiagnosticSource();
		int position = d.getIntPosition();

		StringBuilder sb = new StringBuilder();
		if (position == Position.NOPOS)
			sb.append("-");
		else {
			sb.append(source.getName() + ":" + source.getLineNumber(position)
					+ ":" + source.getColumnNumber(position) + ":");
		}
		sb.append(" ");
		sb.append(d.getCode());
		String sep = ": ";
		for (Object arg : d.getArgs()) {
			sb.append(sep);
			if (arg instanceof JCDiagnostic) {
				sb.append('(');
				sb.append(format_raw((JCDiagnostic) arg));
				sb.append(')');
			} else if (arg instanceof BaseFileObject)
				sb.append(((BaseFileObject) arg).getShortName());
			else if (arg instanceof JavaFileObject)
				sb.append(BaseFileObject.getSimpleName((JavaFileObject) arg));
			else
				sb.append(arg);
			sep = ", ";
		}
		return sb.toString();
	}

	private String format_std(JCDiagnostic d) {
		DiagnosticSource source = d.getDiagnosticSource();
		DiagnosticType type = d.getType();
		int position = d.getIntPosition();

		String format = noPosFormat;
		if (source != null) {
			if (position != Position.NOPOS) {
				format = posFormat;
			} else if (source.getFile() != null
					&& source.getFile().getKind() == JavaFileObject.Kind.CLASS) {
				format = classFormat;
			}
		}

		StringBuilder sb = new StringBuilder();

		for (int i = 0; i < format.length(); i++) {
			char c = format.charAt(i);
			if (c == '%' && i < format.length() - 1) {
				c = format.charAt(++i);
				switch (c) {
				case 'b':
					sb.append(source == null ? "-" : source.getName());
					break;

				case 'e':
					sb.append(position == Position.NOPOS ? "-" : String
							.valueOf(d.getEndPosition()));
					break;

				case 'f':
					sb.append(source == null ? "-" : d.getSourceName());
					break;

				case 'l':
					sb.append(position == Position.NOPOS ? "-" : String
							.valueOf(d.getLineNumber()));
					break;

				case 'c':
					sb.append(position == Position.NOPOS ? "-" : String
							.valueOf(d.getColumnNumber()));
					break;

				case 'o':
					sb.append(position == Position.NOPOS ? "-" : String
							.valueOf(position));
					break;

				case 'p':
					sb.append(d.getPrefix());
					break;

				case 's':
					sb.append(position == Position.NOPOS ? "-" : String
							.valueOf(d.getStartPosition()));
					break;

				case 't': {
					boolean usePrefix;
					switch (type) {
					case FRAGMENT:
						usePrefix = false;
						break;

					case ERROR:
						usePrefix = (position == Position.NOPOS);
						break;

					default:
						usePrefix = true;
					}

					if (usePrefix)
						sb.append(d.getPrefix());
					break;
				}

				case 'm':
					sb.append(d.getMessage(null));
					break;

				case '_':
					sb.append(' ');
					break;

				case '%':
					sb.append('%');
					break;

				default:
					sb.append(c);
					break;
				}
			} else
				sb.append(c);
		}
		return sb.toString();
	}
}
